/*
** $Id: lmem.c $
** 内存管理
** See Copyright Notice in lua.h
*/

#define lmem_c
#define LUA_CORE

#include "lprefix.h"


#include <stddef.h>

#include "lua.h"

#include "ldebug.h"
#include "ldo.h"
#include "lgc.h"
#include "lmem.h"
#include "lobject.h"
#include "lstate.h"



/*
** realloc方法:
** void *frealloc (void *ud, void *ptr, size_t osize, size_t nsize);
** ('osize': 原大小, 'nsize': 新大小)
**
** - frealloc(ud, p, x, 0)  释放‘p'的内存块并返回NULL
** 特别的, frealloc(ud, NULL, 0, 0) 什么都不做,相当于ISO C 中的 free(NULL)
**
** - frealloc(ud, NULL, x, s) 创建一个’s'大小的内存块
** (与'x'无关). 如果不能创建新内存块则返回NULL
**
** - 否则, frealloc(ud, b, x, y) 重新将’b'内存块的大小从‘x'大小变成’y'大小，如果不能重新分配到新大小则返回NULL
*/


/*
** 调用内存分配函数
*/
#define callfrealloc(g,block,os,ns)    ((*g->frealloc)(g->ud, block, os, ns))


/*
** 当分配失败，将进行一次紧急收集后再进行尝试，除非他无法运行收集
** GC不应该在state还没有完全构建好时被调用，因为GC也没有被完全初始化
** 并且，他不应该在‘gcstopem’为true时被调用，因为这时interpreter处于收集步骤中
*/
#define cantryagain(g)	(completestate(g) && !g->gcstopem)




#if defined(EMERGENCYGCTESTS)
/*
** 紧急收集测试：第一次分配将失败，除非是释放内存块(释放永远不会失败)或不能再次尝试，失败将触发’tryagain‘并且每一次分配都将进行一次完整的GC周期
*/
static void *firsttry (global_State *g, void *block, size_t os, size_t ns) {
  if (ns > 0 && cantryagain(g))
    return NULL;  /* fail */
  else  /* normal allocation */
    return callfrealloc(g, block, os, ns);
}
#else
// 第一次分配尝试
#define firsttry(g,block,os,ns)    callfrealloc(g, block, os, ns)
#endif



/*
** {==================================================================
** 为解析器分配/释放数组的函数
** ===================================================================
*/

/*
** 解析期间数组的最小大小，以避免重新分配大小为1、2和4的开销。
** 所有这些数组将被重新分配到确切的大小，或者在解析结束时被擦除
*/
#define MINSIZEARRAY	4

/*
** block: 内存块
** nelems: 元素数量
** psize: 指向存储大小(可存储元素个数的大小)的指针
** size_elems: 每个元素的大小
** limit: 限制大小
** what: 描述元素的字符串
*/
void *luaM_growaux_ (lua_State *L, void *block, int nelems, int *psize,
                     int size_elems, int limit, const char *what) {
  void *newblock;
  int size = *psize;
  if (nelems + 1 <= size)  /* 有存储一个元素的额外空间? */
    return block;  /* 啥也不做 */
  if (size >= limit / 2) {  /* 不能扩为2倍? */
    if (l_unlikely(size >= limit))  /* 扩不了? */
      luaG_runerror(L, "too many %s (limit is %d)", what, limit);
    size = limit;  /* 至少还有一个空位(扩到最大值) */
  }
  else {
    size *= 2;
    if (size < MINSIZEARRAY)
      size = MINSIZEARRAY;  /* 数组下限值 */
  }
  lua_assert(nelems + 1 <= size && size <= limit);
  /* 'limit'确保乘法不会溢出 */
  newblock = luaM_saferealloc_(L, block, cast_sizet(*psize) * size_elems,
                                         cast_sizet(size) * size_elems);
  *psize = size;  /* update only when everything else is OK */
  return newblock;
}


/*
** 在函数原型中，数组的大小也是他的元素数量(为了节省内存),所以，如果他不能将数组缩小到所需的元素数量，那么唯一的选择就是引发错误
*/
void *luaM_shrinkvector_ (lua_State *L, void *block, int *size,
                          int final_n, int size_elem) {
  void *newblock;
  size_t oldsize = cast_sizet((*size) * size_elem);
  size_t newsize = cast_sizet(final_n * size_elem);
  lua_assert(newsize <= oldsize);
  newblock = luaM_saferealloc_(L, block, oldsize, newsize);
  *size = final_n;
  return newblock;
}

/* }================================================================== */


l_noret luaM_toobig (lua_State *L) {
  luaG_runerror(L, "memory allocation error: block too big");
}


/*
** 释放内存
*/
void luaM_free_ (lua_State *L, void *block, size_t osize) {
  global_State *g = G(L);
  lua_assert((osize == 0) == (block == NULL));
  callfrealloc(g, block, osize, 0);
  g->GCdebt -= osize;
}


/*
** 一些分配错误的情况下，这个方法将用来做一次紧急收集以释放内存然后重新分配
*/
static void *tryagain (lua_State *L, void *block,
                       size_t osize, size_t nsize) {
  global_State *g = G(L);
  if (cantryagain(g)) {
    luaC_fullgc(L, 1);  /* 尝试收集一些内存... */
    return callfrealloc(g, block, osize, nsize);  /* 再次尝试分配 */
  }
  else return NULL;  /* 不能运行一次紧急收集 */
}


/*
** 通用realloc
*/
void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) {
  void *newblock;
  global_State *g = G(L);
  lua_assert((osize == 0) == (block == NULL));
  newblock = firsttry(g, block, osize, nsize);
  if (l_unlikely(newblock == NULL && nsize > 0)) {
    newblock = tryagain(L, block, osize, nsize);
    if (newblock == NULL)  /* 内存仍然不足? */
      return NULL;  /* 不更新 'GCdebt' */
  }
  lua_assert((nsize == 0) == (newblock == NULL));
  g->GCdebt = (g->GCdebt + nsize) - osize;
  return newblock;
}

// 分配函数的safe版本
void *luaM_saferealloc_ (lua_State *L, void *block, size_t osize,
                                                    size_t nsize) {
  void *newblock = luaM_realloc_(L, block, osize, nsize);
  if (l_unlikely(newblock == NULL && nsize > 0))  /* allocation failed? */
    luaM_error(L);
  return newblock;
}


// malloc
void *luaM_malloc_ (lua_State *L, size_t size, int tag) {
  if (size == 0)
    return NULL;  /* that's all */
  else {
    global_State *g = G(L);
    void *newblock = firsttry(g, NULL, tag, size);
    if (l_unlikely(newblock == NULL)) {
      newblock = tryagain(L, NULL, tag, size);
      if (newblock == NULL)
        luaM_error(L);
    }
    g->GCdebt += size;
    return newblock;
  }
}
